from enum import Enum, auto
from datetime import datetime
from random import choice


class ID_BaseHelper():
    ID_LENGTH = 18

    @staticmethod
    def __charSet__(index : int) -> set:
        if index == ID_BaseHelper.ID_LENGTH - 1:
            return set([str(i) for i in range(10)]).union({'x', 'X'})
        else:
            return set([str(i) for i in range(10)])

    @staticmethod
    def __convertToNum__(id : str) -> list:
        nums = []
        for index in range(ID_BaseHelper.ID_LENGTH - 1):
            nums.append(int(id[index]))

        last_char = id[ID_BaseHelper.ID_LENGTH - 1]
        nums.append(10 if last_char in {'x', 'X'} else int(last_char))

        return nums


class CheckerReturn(Enum):
    VALID_ID = auto()
    INVALID_LENGTH = auto()
    INVALID_CHARACTER = auto()
    INVALID_DATE = auto()
    INVALID_MATH = auto()


class ID_Checker(ID_BaseHelper):
    WEIGHT_LIST = [(2 ** i) % 11 for i in range(ID_BaseHelper.ID_LENGTH)]

    @staticmethod
    def __isLengthValid__(id : str) -> bool:
        return len(id) == ID_Checker.ID_LENGTH


    @staticmethod
    def __isCharacterValid__(id : str) -> bool:
        for index in range(ID_Checker.ID_LENGTH):
            charset = ID_Checker.__charSet__(index)
            if id[index] not in charset:
                return False

        return True


    @staticmethod
    def __isDateValid__(id : str) -> bool:
        today = datetime.now().date()
        try:
            id_date = datetime.strptime(id[6:6 + 8], "%Y%m%d").date()

            return id_date <= today
        except:
            return False


    @staticmethod
    def __isMathValid__(id : str) -> bool:
        nums = ID_Checker.__convertToNum__(id)
        nums.reverse()

        sum_value = 0
        for num, weight in zip(nums, ID_Checker.WEIGHT_LIST):
            sum_value += num * weight

        return sum_value % 11 == 1


    @staticmethod
    def check(id : str):
        if not ID_Checker.__isLengthValid__(id):
            return CheckerReturn.INVALID_LENGTH

        if not ID_Checker.__isCharacterValid__(id):
            return CheckerReturn.INVALID_CHARACTER

        if not ID_Checker.__isDateValid__(id):
            return CheckerReturn.INVALID_DATE

        if not ID_Checker.__isMathValid__(id):
            return CheckerReturn.INVALID_MATH

        return CheckerReturn.VALID_ID


class ID_Guesser(ID_BaseHelper):
    @staticmethod
    def __guess__(id : str, result : set, depth = 1, upper_index = -1):
        if depth == 0:
            checker_return = CheckerReturn.VALID_ID
            if id not in result:
                checker_return = ID_Checker.check(id)
                if checker_return == CheckerReturn.VALID_ID:
                    result.add(id)

            return checker_return

        start_index = upper_index + 1
        charset = ID_Guesser.__charSet__(0)
        for index in range(start_index, 6 + 8):
            for c in charset:
                try_id = id[:index] + c + id[index + 1 :]
                ID_Guesser.__guess__(try_id, result, depth - 1, index)

        for index in range(max(start_index, 6 + 8), ID_Guesser.ID_LENGTH - 1):
            for c in charset:
                try_id = id[:index] + c + id[index + 1 :]
                if ID_Guesser.__guess__(try_id, result, depth - 1, index) == CheckerReturn.INVALID_DATE:
                    return CheckerReturn.INVALID_DATE

        if start_index <= ID_Guesser.ID_LENGTH - 1:
            charset = ID_Guesser.__charSet__(ID_Guesser.ID_LENGTH - 1)
            charset.remove('X')
            for c in charset:
                try_id = id[:-1] + c
                if ID_Guesser.__guess__(try_id, result, depth - 1, ID_Guesser.ID_LENGTH - 1) == CheckerReturn.INVALID_DATE:
                    return CheckerReturn.INVALID_DATE


    @staticmethod
    def guess(id : str, depth = 1) -> set:
        result = set()
        ID_Guesser.__guess__(id, result, depth = depth)

        return result


class ID_Confuser(ID_BaseHelper):
    @staticmethod
    def __countDiff__(a : str, b : str) -> int:
        count = 0
        for x, y in zip(a, b):
            count += x != y

        return count


    @staticmethod
    def generate(id : str, depth = 1) -> set:
        if ID_Checker.check(id) != CheckerReturn.VALID_ID:
            return set()

        charset = ID_Confuser.__charSet__(ID_Confuser.ID_LENGTH - 1)
        charset.remove(id[-1])

        guess = ID_Guesser.guess(id[:-1] + choice(list(charset)), depth = depth)
        to_remove_ids = []
        for guess_id in guess:
            if ID_Confuser.__countDiff__(id, guess_id) < depth:
                to_remove_ids.append(guess_id)

        for to_remove_id in to_remove_ids:
            guess.remove(to_remove_id)

        return guess



from .command_processer import BaseTextCommandProcesser

class ID_CommandProcesser(BaseTextCommandProcesser):
    commands = {
        "身份证号验证",
        "身份证号猜测",
        "身份证号生成"
    }

    def processChecking(self) -> str:
        return_dict = {
            CheckerReturn.VALID_ID : '身份证号有效',
            CheckerReturn.INVALID_CHARACTER : '身份证号带有无效字符！',
            CheckerReturn.INVALID_DATE : '身份证号所记载的日期不正确！',
            CheckerReturn.INVALID_LENGTH : '身份证号长度不正确！',
            CheckerReturn.INVALID_MATH : '身份证号不通过校验！'
        }
        check_return = ID_Checker.check(self.command_splits[1])

        return return_dict[check_return]


    def validGuessArgs(self, for_what : str = '猜测') -> str:
        if len(self.command_splits) < 2:
            return f'请输入要{for_what}的身份证号模板！'

        if len(self.command_splits[1]) != ID_BaseHelper.ID_LENGTH:
            return '请输入正确的身份证号模板！'

        return '参数正常'


    def processGuessing(self) -> str:
        guess = ID_Guesser.guess(self.command_splits[1])

        return '\n'.join(guess) + f'\n共猜测 {len(guess)} 种情况'


    def processConfusing(self) -> str:
        wrong_ids = ID_Confuser.generate(self.command_splits[1])

        return '\n'.join(wrong_ids) + f'\n共生成 {len(wrong_ids)} 种情况'


    def Process(self) -> str:
        if self.command == '身份证号验证':
            if len(self.command_splits) < 2:
                return '请输入要验证的身份证号！'

            return self.processChecking()
        elif self.command == '身份证号猜测':
            valid_ret = self.validGuessArgs(for_what = '猜测')
            if valid_ret != '参数正常':
                return valid_ret

            return self.processGuessing()

        elif self.command == '身份证号生成':
            valid_ret = self.validGuessArgs(for_what = '生成')
            if valid_ret != '参数正常':
                return valid_ret

            return self.processConfusing()


if __name__ == '__main__':
    g = ID_Guesser.guess('610101197608038790', depth = 1)
    print(len(g))
